// Client.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include "conio.h"

#pragma comment(lib, "ws2_32.lib")

#define STRLEN 4096
#define SERVPORT 5400
#define CLIUSERLEN 8
#define WSVERS MAKEWORD(2, 2)
//#define _WINSOCK_DEPRECATED_NO_WARNINGS

#define USER_NOT_EXISTS "400"
#define INCORRECT_PASSWORD "401"
#define USER_NOT_FOUND "4040"
#define NOT_ONLINE "4041"
#define REJECT_CHATTING "4042"
#define IS_ONLINE "2000"
#define START_CHATTING "2001"

unsigned WINAPI sendmsg(void * linksock);
unsigned WINAPI recvmsg(void * linksock);

char tempBuf[STRLEN]; // global string buffer
int recvThreadBlock = 0;

void testSend(int messageLen);

int logIn(SOCKET linksock);

void oneToOneChatStart(SOCKET linksock);

void chat(SOCKET linksock);

int main(int argc, char *argv[]) {
    SOCKET cli;
    struct sockaddr_in servSockIn;
    HANDLE  hsendThread, hrecvThread;
    WSADATA wsadata;
    int recvMessageLen;
    if (WSAStartup(WSVERS, &wsadata) != 0) {
        printf("failed to load win socket\n");
        return 1;
    }

    // server address
    memset(&servSockIn, 0, sizeof(servSockIn));
    servSockIn.sin_family = AF_INET;
    servSockIn.sin_addr.s_addr = inet_addr(argv[1]);
    servSockIn.sin_port = htons(SERVPORT);

    // create socket
    cli = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (cli == INVALID_SOCKET) {
        printf("socket() failed: %d\n", WSAGetLastError());
        return 1;
    }

    // connect to server
    printf("connect to %s:%d\n", inet_ntoa(servSockIn.sin_addr), ntohs(servSockIn.sin_port));

    if (connect(cli, (SOCKADDR*)&servSockIn, sizeof(servSockIn)) == INVALID_SOCKET) {
        printf("connection failed: %d\n", WSAGetLastError());
        return 1;
    }
    else {
        printf("connected...\n");
    }

    // log in
    if (logIn(cli) < 0) {
        // log in failed.
        printf("log in failed. This cli will quit.\n");
        return 1;
    }

    // get online user list
    memset(&tempBuf, 0, sizeof(tempBuf));
    recvMessageLen = recv(cli, tempBuf, sizeof(tempBuf), 0);
    if (recvMessageLen > 0) printf("%s", tempBuf);

    // chating
    hsendThread = (HANDLE)_beginthreadex(NULL, 0, &sendmsg, (void *)&cli, 0, NULL);
    hrecvThread = (HANDLE)_beginthreadex(NULL, 0, &recvmsg, (void *)&cli, 0, NULL);

    WaitForSingleObject(hsendThread, INFINITE);
    WaitForSingleObject(hrecvThread, INFINITE);

    // log out
    closesocket(cli);
    WSACleanup();

    return 0;
}

// send-message thread
unsigned WINAPI sendmsg(void * linksock) {
    SOCKET hsock = *((SOCKET*)linksock);
    char wBuf[STRLEN];

    while (true) {
        fputs("Please input a function: \n", stdout);
        fputs("'quit' to escape\n", stdout);
        fputs("'chat' someone \n", stdout);
        fputs("'call' someone \n", stdout);
        fputs("'list' all the users \n", stdout);
        fgets(wBuf, STRLEN, stdin);

        // confirm chatting request
        if (strstr(wBuf, "chat confirm\n") != NULL) {
            // confirm to start chatting by request
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "chat confirm");
            int messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            oneToOneChatStart(hsock);
        }
        if (strstr(wBuf, "chat reject\n") != NULL) {
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "chat reject");
            int messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
        }
        // request to chat with someone
        if (strstr(wBuf, "chat\n") != NULL) {
            chat(hsock);
        }
        else {

            // request to call
            //      if (strncmp(wBuf, "call\n", 7) == 0) {
            //          call(hsock);
            //      }

            // request to list
            //      if (strncmp(wBuf, "list\n", 7) == 0) {
            //          list(hsock);
            //      }

            int messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // log out
            if (strncmp(wBuf, "quit\n", 7) == 0) {
                printf("log out successfully. \n");
                break;
            }
        }
    }

    closesocket(hsock);
    return 0;
}

// recv-message thread
unsigned WINAPI recvmsg(void * linksock) {
    SOCKET  lsock = *((SOCKET*)linksock);
    char    message[STRLEN + 10];
    char    recvreq[STRLEN + 1];
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;

    // memset(&cln_addr, 0, sizeof(cln_addr));
    // cln_addr_len = sizeof(cln_addr);
    // int ret = getsockname(lsock, (sockaddr*)&cln_addr, &cln_addr_len);
    // sprintf(recvreq, "user IP: %s port: %d %s", inet_ntoa(cln_addr.sin_addr), cln_addr.sin_port, message);

    while (true) {

        // block for send thread
        while (recvThreadBlock) {
            continue;
        }

        memset(message, 0, STRLEN);
        int recvlen = recv(lsock, message, STRLEN, 0);
        if (recvlen == SOCKET_ERROR || recvlen == 0) {
            break;
        }
        else if (recvlen > 0) {
            message[recvlen] = '\0';
            // if (strstr(message, recvreq) != NULL)
            //  break;
            printf("%s", message);
        }
    }

    closesocket(lsock);
    return 0;
}

void testSend(int messageLen) {
    switch (messageLen) {
    case SOCKET_ERROR: printf("send() failed: %d\n", WSAGetLastError());
    case 0: break;
    default: printf("send successfully. \n", messageLen);
    }
    return;
}

int logIn(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    recvmsg[STRLEN];
    char    sendmsg[STRLEN];
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;
    int logState;

    // send username
    memset(sendmsg, 0, sizeof(sendmsg));
    fputs("Please input username: ", stdout);
    fgets(sendmsg, sizeof(sendmsg), stdin);
    // strncpy(sendmsg, username, 5);
    // printf("%s\n", sendmsg);
    int messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
    testSend(messageLen);

    // recv reply
    memset(recvmsg, 0, sizeof(recvmsg));
    int recvlen = recv(lsock, recvmsg, sizeof(recvmsg), 0);

    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        memset(sendmsg, 0, sizeof(sendmsg));
        sprintf(sendmsg, "log in failed. \n");
        printf("%s", sendmsg);
        return -1;
    }
    else if (recvlen > 0) {
        // user not exists.
        logState = (strstr(recvmsg, USER_NOT_EXISTS) != NULL);
        switch (logState) {
        case 1: printf("user not exists. \n"); return -1; break;
        default: printf("user found. \n"); printf("%s", recvmsg);
        }

        // send password
        memset(sendmsg, 0, sizeof(sendmsg));
        fgets(sendmsg, sizeof(sendmsg), stdin);
        // strncpy(sendmsg, password, 5);
        // printf("%s\n", sendmsg);
        messageLen = send(lsock, sendmsg, strlen(sendmsg), 0);
        testSend(messageLen);

        memset(recvmsg, 0, sizeof(recvmsg));
        recvlen = recv(lsock, recvmsg, sizeof(recvmsg), 0);

        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            memset(sendmsg, 0, sizeof(sendmsg));
            sprintf(sendmsg, "log in failed. \n");
            printf("%s", sendmsg);
            return -1;
        }
        else if (recvlen > 0) {
            // wait for reply

            logState = (strstr(recvmsg, INCORRECT_PASSWORD) != NULL);
            switch (logState) {
            case 1: printf("incorrect password. \n"); return -1; break;
            default: printf("correct password. \n");
            }

            printf("%s", recvmsg);
            return 1;
        }
    }
}

void chat(SOCKET linksock) {
    SOCKET lsock = linksock;
    char wBuf[STRLEN];
    char rBuf[STRLEN];
    int messageLen;
    int recvlen;
    recvThreadBlock = 1; // block recv thread

    // send chat request
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "chat");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // wait for reply from server
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);

    // recv response
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("start chatting failed: response not received. \n");
        return;
    }
    else if (recvlen > 0) {
        rBuf[recvlen] = '\0';
        printf("%s", rBuf);
    }

    // send choice
    memset(wBuf, 0, sizeof(wBuf));
    fgets(wBuf, STRLEN, stdin);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    if (strstr(wBuf, "one") != NULL) {
        // one-to-one chatting

        // wait for destination username request
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
        // recv destination
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            printf("start chatting failed: destination request not received. \n");
            recvThreadBlock = 0; // release lock
            return;
        }
        else if (recvlen > 0) {
            rBuf[recvlen] = '\0';
            printf("%s", rBuf);
        }

        // send destination username
        memset(wBuf, 0, sizeof(wBuf));
        fgets(wBuf, STRLEN, stdin);
        messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);
        wBuf[messageLen - 1] = '\0';

        // wait for destination-username response
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
        // diff response code
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            printf("start chatting failed: destination response not received. \n");
            recvThreadBlock = 0; // release lock
            return;
        }
        else if (recvlen > 0) {
            rBuf[recvlen] = '\0';

            // destination user not found
            if (strstr(rBuf, USER_NOT_FOUND) != NULL) {
                printf("user %s is not found. \n", wBuf);
                recvThreadBlock = 0; // release lock
                return;
            }
            // destination user not online
            else if (strstr(rBuf, NOT_ONLINE) != NULL) {
                printf("user %s isn't online. \n", wBuf);
                recvThreadBlock = 0; // release lock
                return;
            }

            else if (strstr(rBuf, IS_ONLINE) != NULL) {
                printf("user %s is online. \n", wBuf);

                // wait for chat confirmed
                memset(rBuf, 0, sizeof(wBuf));
                recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
                if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                    printf("start chatting failed: chatting confirm not received. \n");
                    recvThreadBlock = 0; // release lock
                    return;
                }
                else if (recvlen > 0) {
                    // chatting rejected
                    if (strstr(rBuf, REJECT_CHATTING) != NULL) {
                        printf("but rejected your chatting request. \n");
                        recvThreadBlock = 0; // release lock
                        return;
                    }
                    else if (strstr(rBuf, START_CHATTING) != NULL) {
                        // one-to-one chatting
                        recvThreadBlock = 0;
                        oneToOneChatStart(lsock);
                    }
                }
            }
        }
    }
    else if (strstr(rBuf, "group") != NULL) {
        // more-to-more chatting
        // 
    } else {
        printf("start chatting failed: chat-type not supported. \n");
    }

    recvThreadBlock = 0; // release lock
    return;
}

void oneToOneChatStart(SOCKET linksock) {
    SOCKET lsock = linksock;
    char wBuf[STRLEN];
    int messageLen;

    while (true) {
        fputs("chatting...('quit' ot quit)\n", stdout);
        fgets(wBuf, STRLEN, stdin);
        int messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);

        // quit chatting
        if (strncmp(wBuf, "quit\n", 7) == 0) {
            printf("you leave chatting. \n");
            break;
        }
    }

    return;
}

